using System;
using System.Collections.Generic;
using System.Text;
using Terminal.Advanced_AEUC_settings_controls.Settings_Interfaces;
using Terminal.Advanced_AEUC_settings_controls.Settings_AUX;
using System.Windows.Forms;
using System.IO;
using Terminal.Advanced_AEUC_settings_controls.Setting_Advanced_Control;

namespace Terminal.Advanced_AEUC_settings_controls.Settings_AUX
{
    /// <summary>
    /// A constructed message to be sent to the AEUC target
    /// </summary>
    public class GenMSG : IAEUCSerialization
    {
        /// <summary>
        /// A reference to the parent tab control used mainly (only so far) in serialization
        /// </summary>
        AEUCTabbedParentControl parentTabControl;
        /// <summary>
        /// Id of the message
        /// </summary>
        public GenMSGId msgId;
        /// <summary>
        /// Status of the message
        /// </summary>
        public GenMSGStatus msgStatus;
        /// <summary>
        /// Message target
        /// </summary>
        public GenMSGDestination msgDestination;
        /// <summary>
        /// Custom display name for the message
        /// </summary>
        public string msgDisplayName;
        #region Specific controls list
        /// <summary>
        /// Flag indicating if specific controls should be used for the payload generation
        /// </summary>
        public bool UseSpecControls = false;
        /// <summary>
        /// List of specific controls reference ids for payload generation (only used when UseSpecControls is set to true)
        /// </summary>
        public List<int> spec_control_references;
        /// <summary>
        /// List of specific controls instance pointers for payload generation (only used when UseSpecControls is set to true)
        /// </summary>
        public List<IAEUCSetting> AssociatedControlSettings;
        #endregion
        #region Specific tab page
        /// <summary>
        /// Flag indicating if specific tab page should be used for the payload generation
        /// </summary>
        public bool UseSpecTabPage = false;
        /// <summary>
        /// Tab page reference id for payload generation (only used when UseSpecTabPage is set to true)
        /// </summary>
        public int spec_tab_page_reference;
        /// <summary>
        /// Tab page instance pointer for payload generation (only used when UseSpecTabPage is set to true)
        /// </summary>
        public TabPage AssociatedTabPage;
        #endregion
        /// <summary>
        /// Flag indicating if payload will be only script dependand (deprecated)
        /// </summary>
        public bool UseScriptAssignedSettings = false;
        /// <summary>
        /// Flag indicating if settings should be postfixed by a setting delimiter byte (not used)
        /// </summary>
        public bool AppendSetEndTrailer = false;
        #region Specific single value
        /// <summary>
        /// Flag indicating if a special value should be used for payload
        /// </summary>
        public bool UseSpecVal = false;
        /// <summary>
        /// Special payload value (used only when UseSpecVal is set to true)
        /// </summary>
        public int SpecVal;
        /// <summary>
        /// Special payload value's size (used only when UseSpecVal is set to true)
        /// </summary>
        public uint SpecValSize;
        #endregion
        /// <summary>
        /// True if the message is displayed in the quick access messages menu (currently not functional)
        /// </summary>
        public bool ShowInMenu = false;

        /// <summary>
        /// Scripting payload object - used for general payload generation.
        /// </summary>
        public GenPayloadStruct payloadStruct;

        /// <summary>
        /// Byte array containing the complete message byte stream
        /// </summary>
        public byte[] msgBytestream;

        /// <summary>
        /// The payload size field of the message (either read from bytestream or set when composing message)
        /// </summary>
        public UInt64 msgPayloadSize = 0;

        #region Constructors
        /// <summary>
        /// Constructor creating an empty message
        /// </summary>
        public GenMSG()
        {
        }
        /// <summary>
        /// Constructor taking only a reference to the parent control
        /// </summary>
        /// <param name="iparentTabControl">The AEUC tabbed parent control holder</param>
        public GenMSG(AEUCTabbedParentControl iparentTabControl)
        {
            parentTabControl = iparentTabControl;
        }
        /// <summary>
        /// Constructor associating message with a control group
        /// </summary>
        /// <param name="iparentTabControl">Reference to the control holder parent</param>
        /// <param name="imsgId">Message id</param>
        /// <param name="imsgStatus">Message status</param>
        /// <param name="imsgDestination">Message destination</param>
        /// <param name="iAssociatedTabPage">Reference to the tab page that the controls belong to</param>
        /// <param name="iAssociatedControlSettings">List of controls to include in payload</param>
        /// <param name="iAppendSetEndTrailer">Should there be an end of settings delimiter appended</param>
        public GenMSG(AEUCTabbedParentControl iparentTabControl,GenMSGId imsgId, GenMSGStatus imsgStatus, GenMSGDestination imsgDestination, TabPage iAssociatedTabPage, List<IAEUCSetting> iAssociatedControlSettings, bool iAppendSetEndTrailer)
        {
            parentTabControl = iparentTabControl;
            msgId = imsgId;
            msgStatus = imsgStatus;
            msgDestination = imsgDestination;
            UseSpecControls = true;
            AssociatedTabPage = iAssociatedTabPage;
            AssociatedControlSettings = iAssociatedControlSettings;
            AppendSetEndTrailer = iAppendSetEndTrailer;
        }
        /// <summary>
        /// Constructor associating the message with a specific tab page
        /// </summary>
        /// <param name="iparentTabControl">Reference to the control holder parent</param>
        /// <param name="imsgId">Message id</param>
        /// <param name="imsgStatus">Message status</param>
        /// <param name="imsgDestination">Message destination</param>
        /// <param name="iAssociatedTabPage">Tab page to collect settings from</param>
        /// <param name="iAppendSetEndTrailer">Should there be an end of settings delimiter appended</param>
        public GenMSG(AEUCTabbedParentControl iparentTabControl, GenMSGId imsgId, GenMSGStatus imsgStatus, GenMSGDestination imsgDestination, TabPage iAssociatedTabPage, bool iAppendSetEndTrailer)
        {
            parentTabControl = iparentTabControl;
            msgId = imsgId;
            msgStatus = imsgStatus;
            msgDestination = imsgDestination;
            UseSpecTabPage = true;
            AssociatedTabPage = iAssociatedTabPage;
            AppendSetEndTrailer = iAppendSetEndTrailer;
        }
        /// <summary>
        /// Constructor associating the message with the currently selected tab page
        /// </summary>
        /// <param name="iparentTabControl">Reference to the control holder parent</param>
        /// <param name="imsgId">Message id</param>
        /// <param name="imsgStatus">Message status</param>
        /// <param name="imsgDestination">Message destination</param>
        /// <param name="iAppendSetEndTrailer">Should there be an end of settings delimiter appended</param>
        public GenMSG(AEUCTabbedParentControl iparentTabControl, GenMSGId imsgId, GenMSGStatus imsgStatus, GenMSGDestination imsgDestination, bool iAppendSetEndTrailer)
        {
            parentTabControl = iparentTabControl;
            msgId = imsgId;
            msgStatus = imsgStatus;
            msgDestination = imsgDestination;
            AppendSetEndTrailer = iAppendSetEndTrailer;
        }
        /// <summary>
        /// Constructor setting a specific value to be used with the message
        /// </summary>
        /// <param name="iparentTabControl">Reference to the control holder parent</param>
        /// <param name="imsgId">Message id</param>
        /// <param name="imsgStatus">Message status</param>
        /// <param name="imsgDestination">Message destination</param>
        /// <param name="iSpecVal">The specified value</param>
        /// <param name="iSpecValSize">Size in bytes of the specific value</param>
        /// <param name="iAppendSetEndTrailer">Should there be an end of settings delimiter appended</param>
        public GenMSG(AEUCTabbedParentControl iparentTabControl, GenMSGId imsgId, GenMSGStatus imsgStatus, GenMSGDestination imsgDestination, int iSpecVal, uint iSpecValSize, bool iAppendSetEndTrailer)
        {
            parentTabControl = iparentTabControl;
            msgId = imsgId;
            msgStatus = imsgStatus;
            msgDestination = imsgDestination;
            UseSpecVal = true;
            SpecVal = iSpecVal;
            SpecValSize = iSpecValSize;
            AppendSetEndTrailer = iAppendSetEndTrailer;
        }
        /// <summary>
        /// Constructor associating the message with the currently selected tab page
        /// </summary>
        /// <param name="iparentTabControl">Reference to the control holder parent</param>
        /// <param name="imsgId">Message id</param>
        /// <param name="imsgStatus">Message status</param>
        /// <param name="imsgDestination">Message destination</param>
        /// <param name="iUseScriptAssignedSettings">Use script assigned control settings</param>
        /// <param name="iAppendSetEndTrailer">Should there be an end of settings delimiter appended</param>
        public GenMSG(AEUCTabbedParentControl iparentTabControl, GenMSGId imsgId, GenMSGStatus imsgStatus, GenMSGDestination imsgDestination, bool iUseScriptAssignedSettings, bool iAppendSetEndTrailer)
        {
            parentTabControl = iparentTabControl;
            msgId = imsgId;
            msgStatus = imsgStatus;
            msgDestination = imsgDestination;
            UseScriptAssignedSettings = iUseScriptAssignedSettings;
            AppendSetEndTrailer = iAppendSetEndTrailer;
        }
        #endregion

        /// <summary>
        /// Updates the message definition to match the input parameter
        /// </summary>
        /// <param name="updateGenMsg">Message definition to copy settings from</param>
        public void GenMSGUpdate(GenMSG updateGenMsg)
        {

            parentTabControl = updateGenMsg.parentTabControl;

            msgId = updateGenMsg.msgId;
            msgStatus = updateGenMsg.msgStatus;
            msgDestination = updateGenMsg.msgDestination;

            UseSpecControls = updateGenMsg.UseSpecControls;
            AssociatedTabPage = updateGenMsg.AssociatedTabPage;
            AssociatedControlSettings = updateGenMsg.AssociatedControlSettings;

            UseScriptAssignedSettings = updateGenMsg.UseScriptAssignedSettings;

            UseSpecTabPage = updateGenMsg.UseSpecTabPage;
            AssociatedTabPage = updateGenMsg.AssociatedTabPage;

            UseSpecVal = updateGenMsg.UseSpecVal;
            SpecVal = updateGenMsg.SpecVal;
            SpecValSize = updateGenMsg.SpecValSize;
            
            AppendSetEndTrailer = updateGenMsg.AppendSetEndTrailer;
        }

        /// <summary>
        /// This function constructs the bitstream that will be sent to the driver
        /// </summary>
        /// <returns>Message bitstream array</returns>
        public byte[] GenerateMessage(byte[] preparedStream)
        {
            #region Header
            BinaryWriter hdr_Writer;
            byte[] msg_header = null;
            #endregion
            #region Payload
            BinaryWriter pld_Writer;
            byte[] msg_payload = null;
            msgPayloadSize = 0;
            #endregion

            if (UseSpecVal)
            {
                #region Write specific value only
                msgPayloadSize = SpecValSize;
                msg_payload = new byte[msgPayloadSize];
                pld_Writer = new BinaryWriter(new MemoryStream(msg_payload));
                switch (SpecValSize)
                {
                    case 1:
                        pld_Writer.Write((Byte)SpecVal);
                        break;
                    case 2:
                        pld_Writer.Write((Int16)SpecVal);
                        break;
                    case 4:
                        pld_Writer.Write((Int32)SpecVal);
                        break;
                }
                #endregion
            }
            else
            {
                if (UseSpecControls)
                {
                    #region Accumulate specific controls settings
                    payloadStruct = new GenPayloadStruct("", AssociatedControlSettings.ToArray());
                    #endregion
                }
                else if (UseSpecTabPage)
                {
                    #region Accumulate specific tab controls settings
                    int tabIndx = parentTabControl.TabPages.IndexOf(AssociatedTabPage);
                    payloadStruct = new GenPayloadStruct("",
                                                        parentTabControl.gtpProperties[tabIndx].GetSettingArray,
                                                        parentTabControl.gtpProperties[tabIndx].TabRelCompId,
                                                        parentTabControl.gtpProperties[tabIndx].TabId,
                                                        parentTabControl.gtpProperties[tabIndx].TabIdExtra);
                    #endregion
                }
                else if (UseScriptAssignedSettings)
                {
                    #region Get script settings

                    //TODO:
                    //payloadSettings = from script input!
                    //Do nothing so far - payloadStruct should be provided by the script module when this is invoked!

                    #endregion
                }

                if (payloadStruct != null)
                {
                    msg_payload = payloadStruct.prepareByteStream();
                    msgPayloadSize = (uint)msg_payload.Length;
                }
                else if (preparedStream != null)
                {
                    msg_payload = preparedStream;
                    msgPayloadSize = (uint)preparedStream.Length;
                }
                else
                {
                    msgPayloadSize = 0;
                }
            } 

            #region Chose appropriate message composition according to the message destination
            #region OMX specific message composition
            msg_header = new byte[12];
            hdr_Writer = new BinaryWriter(new MemoryStream(msg_header));
            hdr_Writer.Write((UInt32)msgId.UID);            //Msg id
            hdr_Writer.Write((UInt32)msgStatus.UID);        //Msg status
            hdr_Writer.Write((UInt32)msgPayloadSize);       //Msg size (payload + header)
            if (msgPayloadSize > 0)                         //<-- If there is any payload associated to this message
            {
                byte[] msgStream = new byte[msg_header.Length + msg_payload.Length];
                msg_header.CopyTo(msgStream, 0);
                msg_payload.CopyTo(msgStream, msg_header.Length);
                msg_header = msgStream;
            }
            #endregion
            #endregion

            return msg_header;  //<-- Message header array contains the whole message byte stream
        }

        /// <summary>
        /// Returns the size of the message header in bytes as found in the bytestream.
        /// </summary>
        /// <returns>Byte size of the message header</returns>
        public int GetMsgHeaderBytestreamSize()
        {
            return 12;
        }

        /// <summary>
        /// Parses a byte array message to the message object
        /// </summary>
        public void ParseMessageData()
        {
            int strmPos = 0;
            byte[] msgData = msgBytestream;

            msgId.UID = 0;
            msgStatus.UID = 0;
            msgPayloadSize = 0;

            #region Get message id
            for (int i = 0; i < 4; i++)
                msgId.UID |= (uint)(msgData[strmPos++] << (8 * i));
            #endregion
            #region Get message status
            for (int i = 0; i < 4; i++)
                msgStatus.UID |= (uint)(msgData[strmPos++] << (8 * i));
            #endregion
            #region Get payload size
            for (int i = 0; i < 4; i++)
                msgPayloadSize |= (uint)(msgData[strmPos++] << (8 * i));
            #endregion

            byte[] payloadData = new byte[msgPayloadSize];
            #region Copy payload data array
            for (uint i = 0; i < msgPayloadSize; i++)
                payloadData[i] = msgData[strmPos++];
            #endregion

            if (payloadStruct == null)
                payloadStruct = new GenPayloadStruct("", null, 0, 0, 0);
            try
            {
                payloadStruct.parseByteStream(payloadData);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        /// <summary>
        /// Parses a byte array message to the message object
        /// </summary>
        public void ParseMessageDataHeaderOnly()
        {
            int strmPos = 0;
            byte[] msgData = msgBytestream;

            msgId.UID = 0;
            msgStatus.UID = 0;
            msgPayloadSize = 0;

            #region Get message id
            for (int i = 0; i < 4; i++)
                msgId.UID |= (uint)(msgData[strmPos++] << (8 * i));
            #endregion
            #region Get message status
            for (int i = 0; i < 4; i++)
                msgStatus.UID |= (uint)(msgData[strmPos++] << (8 * i));
            #endregion
            #region Get payload size
            for (int i = 0; i < 4; i++)
                msgPayloadSize |= (uint)(msgData[strmPos++] << (8 * i));
            #endregion
        }

        /// <summary>
        /// Overrided ToString() method of the class
        /// </summary>
        /// <returns>String representation of the object</returns>
        public override string ToString()
        {
            return msgDisplayName;
        }
        /// <summary>
        /// Resets all of the message tab page and controls references
        /// </summary>
        public void ResetMessageControlTabReferences(TabPage[] TabPages)
        {
            #region Load the specific associated tab page and reference it
            if (spec_tab_page_reference == -1)
            {
                //If value is minus one there is no tab page referencing
                AssociatedTabPage = null;
            }
            else
            {
                //Set the reference to the tab page at the specified array position
                AssociatedTabPage = TabPages[spec_tab_page_reference];
            }
            #endregion

            #region Load the specific associated control list
            AssociatedControlSettings = new List<IAEUCSetting>();
            for (int i = 0; i<spec_control_references.Count; i++)
            {
                //Adds a reference to the specified array position in the controls of the tab page
                AssociatedControlSettings.Add((IAEUCSetting)AssociatedTabPage.Controls[spec_control_references[i]]);
            }
            #endregion
        }

        #region ISerialization Members
        /// <summary>
        /// Current element serialization state version
        /// </summary>
        public int serVersion
        {
            get
            {
                return 0x000;
            }
        }
        /// <summary>
        /// Saves this objec instance to a file
        /// </summary>
        /// <param name="srWriter">Binray writer of the associated file</param>
        void IAEUCSerialization.Serialize(BinaryWriter srWriter)
        {
            srWriter.Write(serVersion);
            Serialize_v000(srWriter);
        }
        /// <summary>
        /// Loads this objects instance from a file
        /// </summary>
        /// <param name="srReader">Binray reader of the associated file</param>
        /// <returns>Boolean representing the success of the operation</returns>
        bool IAEUCSerialization.Deserialize(BinaryReader srReader)
        {
            bool desProcState;
            int serialVer = srReader.ReadInt32();

            switch (serialVer)
            {
                case 0x000:
                    desProcState = Deserialize_v000(srReader);
                    break;
                default:
                    desProcState = false;
                    break;
            }

            return desProcState;
        }

        #region Version 0x000 serialization
        /// <summary>
        /// Version 0x000 serialization
        /// </summary>
        /// <param name="srWriter">File writer</param>
        void Serialize_v000(BinaryWriter srWriter)
        {
            //Auxiliary flag to indicate if the message id/status was found in one of the possible container arrays
            bool found = false;
            #region Find and save the reference number to the message id (searches in both AEUC and SPECMSG message id arrays)
            for (int i = 0; i < parentTabControl.cmcDefinitios.AEUC_MSG_ID.Count; i++)
            {
                if (parentTabControl.cmcDefinitios.AEUC_MSG_ID[i] == msgId)
                {
                    found = true;
                    srWriter.Write(0); // AEUC
                    srWriter.Write(i);
                }
            }
            if (!found)
            {
                for (int i = 0; i < parentTabControl.cmcDefinitios.SPECMSG_MSG_ID.Count; i++)
                {
                    if (parentTabControl.cmcDefinitios.SPECMSG_MSG_ID[i] == msgId)
                    {
                        found = true;
                        srWriter.Write(1); // SPECMSG
                        srWriter.Write(i);
                    }
                }
            }
            if (!found)
            {
                srWriter.Write(-1);
            }
            found = false;
            #endregion
            #region Find and save the reference number to the message status (searches in both AEUC and SPECMSG message status arrays)
            for (int i = 0; i < parentTabControl.cmcDefinitios.AEUC_MSG_STAT.Count; i++)
            {
                if (parentTabControl.cmcDefinitios.AEUC_MSG_STAT[i] == msgStatus)
                {
                    found = true;
                    srWriter.Write(i);
                }
            }
            if (!found)
            {
                for (int i = 0; i < parentTabControl.cmcDefinitios.SPECMSG_MSG_STAT.Count; i++)
                {
                    if (parentTabControl.cmcDefinitios.SPECMSG_MSG_STAT[i] == msgStatus)
                    {
                        found = true;
                        srWriter.Write(i);
                    }
                }
            }
            if (!found)
            {
                srWriter.Write(-1);
            }
            found = false;
            #endregion
            #region Save the reference to the message destination
            for (int i = 0; i < parentTabControl.cmcDefinitios.MSG_DEST.Count; i++)
            {
                if (parentTabControl.cmcDefinitios.MSG_DEST[i] == msgDestination)
                {
                    found = true;
                    srWriter.Write(i);
                }
            }
            if (!found)
            {
                srWriter.Write(-1);
            }
            found = false;
            #endregion

            srWriter.Write(msgDisplayName);
            srWriter.Write(UseSpecControls);
            srWriter.Write(UseSpecTabPage);
            srWriter.Write(UseScriptAssignedSettings);

            #region Find the specific associated tab page in the Main tab control an save its array position for later referencing
            for (int i = 0; i <= parentTabControl.TabPages.Count; i++)
            {
                if (i == parentTabControl.TabPages.Count)
                {
                    srWriter.Write(-1);
                }
                else if (AssociatedTabPage == parentTabControl.TabPages[i])
                {
                    srWriter.Write(i);
                    break;
                }
            }
            #endregion

            #region Find the array positions of all the control associated to this message and save them for later referencing
            if (AssociatedControlSettings != null && AssociatedControlSettings.Count > 0)
            {
                srWriter.Write(AssociatedControlSettings.Count);
                for (int i = 0; i < AssociatedControlSettings.Count; i++)
                {
                    Control specificControl = (Control)AssociatedControlSettings[i];
                    for (int specControls = 0; specControls < AssociatedTabPage.Controls.Count; specControls++)
                    {
                        if (specificControl == AssociatedTabPage.Controls[specControls])
                        {
                            srWriter.Write(specControls);
                        }
                    }
                }
            }
            else
            {
                srWriter.Write(-1);
            }
            #endregion

            srWriter.Write(AppendSetEndTrailer);
            srWriter.Write(UseSpecVal);
            srWriter.Write(SpecVal);
            srWriter.Write(SpecValSize);
            srWriter.Write(ShowInMenu);
        }
        /// <summary>
        /// Version 0x000 deserialization
        /// </summary>
        /// <param name="srReader">File reader</param>
        /// <returns>True if operation is succesful</returns>
        bool Deserialize_v000(BinaryReader srReader)
        {
            int specCaseLoad;
            bool NoError = true;

            try
            {
                #region Read and set the message id reference number
                specCaseLoad = srReader.ReadInt32();
                if (specCaseLoad == -1)
                {
                    msgId = null;
                }
                else
                {
                    switch (specCaseLoad)
                    {
                        case 0:
                            msgId = parentTabControl.cmcDefinitios.AEUC_MSG_ID[srReader.ReadInt32()];
                            break;
                        case 1:
                            msgId = parentTabControl.cmcDefinitios.SPECMSG_MSG_ID[srReader.ReadInt32()];
                            break;
                    }
                }
                #endregion
                #region Read and set the message status reference number
                if (specCaseLoad == -1)
                {
                    msgStatus = null;
                }
                else
                {
                    switch (specCaseLoad)
                    {
                        case 0:
                            msgStatus = parentTabControl.cmcDefinitios.AEUC_MSG_STAT[srReader.ReadInt32()];
                            break;
                        case 1:
                            msgStatus = parentTabControl.cmcDefinitios.SPECMSG_MSG_STAT[srReader.ReadInt32()];
                            break;
                    }
                }
                #endregion
                #region Read and set the message destination reference number
                specCaseLoad = srReader.ReadInt32();
                if (specCaseLoad == -1)
                {
                    msgDestination = new GenMSGDestination();
                }
                else
                {
                    msgDestination = parentTabControl.cmcDefinitios.MSG_DEST[specCaseLoad];
                }
                #endregion

                msgDisplayName = srReader.ReadString();
                UseSpecControls = srReader.ReadBoolean();
                UseSpecTabPage = srReader.ReadBoolean();
                UseScriptAssignedSettings = srReader.ReadBoolean();

                //Read the tab page array position in the parent tab control
                spec_tab_page_reference = srReader.ReadInt32();

                #region Load the specific associated control list
                //Reads the number of controls that are saved
                specCaseLoad = srReader.ReadInt32();
                if (spec_control_references == null)
                    spec_control_references = new List<int>();
                else
                    spec_control_references.Clear();
                for (int i = 0; i < specCaseLoad; i++)
                {
                    //Adds the reference array position
                    spec_control_references.Add(srReader.ReadInt32());
                }
                #endregion

                AppendSetEndTrailer = srReader.ReadBoolean();
                UseSpecVal = srReader.ReadBoolean();
                SpecVal = srReader.ReadInt32();
                SpecValSize = srReader.ReadUInt32();
                ShowInMenu = srReader.ReadBoolean();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                NoError = false;
            }
            return NoError;
        }
        #endregion
        #endregion
    }
}
